﻿#region using statements

using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using XNAGameEngine.Input;
using XNAGameEngine.Logs;

#endregion

namespace XNAGameEngine.GameObject
{
    /// <summary>
    /// Manages the creation, updating and destruction of all game objects
    /// </summary>
    public sealed class ManagerGameObject2D
        : DrawableGameComponent, IManagerGameObject2D
    {
        #region delegates

        /// <summary>
        /// Delegate
        /// </summary>
        /// <param name="objectId"></param>
        public delegate void GameObjectEventHandler(int objectId);

        #endregion

        #region local fields

        /// <summary>
        /// list of all the objects being destroyed this frame
        /// </summary>
        private readonly List<GameObject2D> _destroyedObjectList;

        /// <summary>
        /// list of all the new objects that are being created this frame
        /// </summary>
        private readonly List<GameObject2D> _newObjectList;

        /// <summary>
        /// maps the objects to their id number
        /// </summary>
        private readonly Dictionary<int, GameObject2D> _objectInstanceRegistry;

        /// <summary>
        /// list of all objects that need to be updated each frame
        /// </summary>
        private readonly List<GameObject2D> _dynamicObjectList;

        /// <summary>
        /// game object registry to store all the types of objects and create new objects
        /// </summary>
        private readonly ManagerGameObject2DRegistry _objectTypeRegistry;

        /// <summary>
        /// number of the next unique object id to assign
        /// </summary>
        private int _nextObjectID;

        #endregion

        #region constructors

        /// <summary>
        /// Register the game engine
        /// </summary>
        /// <param name="game">the game engine</param>
        public ManagerGameObject2D(Game game)
            : base(game)
        {
            GameObject2D.SetReferences(Game, this);
            Game.Services.AddService(typeof(IManagerGameObject2D), this);
            Game.Components.Add(this);
            _objectTypeRegistry = new ManagerGameObject2DRegistry(this);
            _objectInstanceRegistry = new Dictionary<int, GameObject2D>();
            _dynamicObjectList = new List<GameObject2D>();
            _destroyedObjectList = new List<GameObject2D>();
            _newObjectList = new List<GameObject2D>();
            ManagerLogs.Write("ManagerGameObject2D : Created");
        }

        #endregion

        #region GameEngineManager methods

        /// <summary>
        /// Initialize the GameEngineManager
        /// </summary>
        public override void Initialize()
        {
            foreach (GameObject2D gameObject in _dynamicObjectList)
                gameObject.Destroy();

            _dynamicObjectList.Clear();

            base.Initialize();
        }

        /// <summary>
        /// Update the Engine Manager
        /// </summary>
        /// <param name="gameTime">the game time</param>
        public override void Update(GameTime gameTime)
        {
            //remove all objects destroyed
            foreach (GameObject2D gameObject in _destroyedObjectList)
            {
                _dynamicObjectList.Remove(gameObject);
                _objectInstanceRegistry.Remove(gameObject._objectId);
            }
            _destroyedObjectList.Clear();

            //add new objects to the list
            foreach (GameObject2D gameObject in _newObjectList)
                _dynamicObjectList.Add(gameObject);
            _newObjectList.Clear();

            //update all objects
            foreach (GameObject2D gameObject in _dynamicObjectList)
                gameObject.Update(gameTime);

            base.Update(gameTime);
        }

        /// <summary>
        /// Draw phase of the manager
        /// </summary>
        public override void Draw(GameTime gameTime)
        {
            GameEngine.SpriteBatch.Begin(
                SpriteBlendMode.AlphaBlend, SpriteSortMode.BackToFront,
                SaveStateMode.SaveState);

            foreach (GameObject2D gameObject in _dynamicObjectList)
                gameObject.Draw(gameTime);

            base.Draw(gameTime);

            GameEngine.SpriteBatch.End();
        }

        #endregion

        #region IManagerGameObjects methods

        /// <summary>
        /// Checks to make sure an object exists
        /// </summary>
        /// <param name="objectId">the id number of the object to check</param>
        /// <returns>true if the object is in the registry, false otherwise</returns>
        public bool GameObjectExists(int objectId)
        {
            return _objectInstanceRegistry.ContainsKey(objectId);
        }

        /// <summary>
        /// Returns the object if it exists
        /// </summary>
        /// <param name="objectId">the object id</param>
        /// <returns>the object whose id was passed in</returns>
        public GameObject2D GetGameObjectById(int objectId)
        {
            if (!GameObjectExists(objectId))
                throw new SystemException(
                    "Object Identifier number " + objectId + " not found");

            return _objectInstanceRegistry[objectId];
        }

        /// <summary>
        /// <para>Register a new game object in the registry.</para>
        /// <para>By default, objects are set as ACTIVE</para>
        /// </summary>
        /// <param name="newObjectKey">
        /// <para>The key value registered to the object to create</para>
        /// <para>It MUST inherit the base GameObject2D class or an exception will be thrown.</para>
        /// <para>EXAMPLE: RegisterNewGameObject("SuperAwesomeBadGuy");</para>
        /// <returns>the unique object id number associated with the new object</returns>
        public int CreateGameObject(string newObjectKey)
        {
            GameObject2D newObject =
                _objectTypeRegistry.CreateObjectInstance(
                    newObjectKey, _nextObjectID);
            _objectInstanceRegistry.Add(newObject._objectId, newObject);
            _newObjectList.Add(newObject);
            _nextObjectID++;
            newObject.Initialize();
            return newObject._objectId;
        }

        /// <summary>
        /// Returns a list containing all of the unique object identifiers 
        /// </summary>
        /// <returns>the list of all unique object identifiers</returns>
        public List<int> GetGameObjectIdList()
        {
            Console.WriteLine(_objectInstanceRegistry.Count);
            return new List<int>(_objectInstanceRegistry.Keys);
        }

        /// <summary>
        /// Destroy all of the objects in the manager
        /// </summary>
        public void DestroyAllGameObjects()
        {
            foreach (GameObject2D gameObject in _dynamicObjectList)
                gameObject.Destroy();
        }

        /// <summary>
        /// Destroys a specific game object
        /// </summary>
        /// <param name="objectId">the id of the game object to destroy</param>
        public void DestroyGameObjectById(int objectId)
        {
            GetGameObjectById(objectId).Destroy();
        }

        /// <summary>
        /// <para>Register a GameObject2D with the registry</para>
        /// <para>IMPORTANT NOTICE: GAMEOBJECT2D OBJECT CONSTRUCTS MUST ONLY CONTAIN THE OBJECTID AS A CONSTRUCTOR PARAMETER.
        /// </para>
        /// <para>AN EXCEPTION WILL BE THROWN OTHERWISE</para>
        /// </summary>
        /// <param name="gameObject">the type to register</param>
        public void RegisterGameObject(string key, Type newObjectType)
        {
            _objectTypeRegistry.RegisterGameObject(key, newObjectType);
        }

        #endregion

        #region IGameObjectEventCallbacks methods

        /// <summary>
        /// <para>This method is a callback for whenever a game object gets destroyed.</para>
        /// <para>By implementing this interface, a class registers to listen for when an object is destroyed.</para>
        /// <para>This is mainly used</para>
        /// </summary>
        /// <param name="objectId">the destroyed objects unique identification number</param>
        public void OnObjectDestroyed(int objectId)
        {
            _destroyedObjectList.Add(GetGameObjectById(objectId));
        }

        #endregion
    }
}